#include <sxQDirectX_11_Viewport.h>
#include <sxViewportController.h>
#include <sxDirectX_11_Context.h>

Sx::Util::ArrayList<Sx::QtInterface::QDirectX_11_Viewport*> Sx::QtInterface::QDirectX_11_Viewport::viewports = Sx::Util::ArrayList<Sx::QtInterface::QDirectX_11_Viewport*>();
Sx::Util::ArrayList<D3D11_VIEWPORT> Sx::QtInterface::QDirectX_11_Viewport::directXViewportSizes = Sx::Util::ArrayList<D3D11_VIEWPORT>();

Sx::QtInterface::QDirectX_11_Viewport::QDirectX_11_Viewport(QWidget* parent, const std::shared_ptr<DirectX_11_Context>& context, unsigned int fps, Qt::WFlags flags) : 
	QViewport(parent, context, fps, flags) {

	this->context = context;
	QDirectX_11_Viewport::viewports.add(this);
	this->setAttribute(Qt::WA_OpaquePaintEvent, true);  
	this->setAttribute(Qt::WA_PaintOnScreen, true);

	this->initialize();
}

Sx::QtInterface::QDirectX_11_Viewport::QDirectX_11_Viewport(QWidget* parent, QDirectX_11_Viewport* share, unsigned int fps, Qt::WFlags flags) :
	QViewport(parent, (share == nullptr ) ? nullptr : share->context, fps, flags) {

	this->context = context;
	QDirectX_11_Viewport::viewports.add(this);
	this->setAttribute(Qt::WA_OpaquePaintEvent, true);  
	this->setAttribute(Qt::WA_PaintOnScreen, true);

	this->initialize();
}

Sx::QtInterface::QDirectX_11_Viewport::~QDirectX_11_Viewport() {
	//--------------------------------------------------------------------------
	// Remove the viewport pointer so that the array of viewport dimensions
	// can be provided to the DirectX device.
	//--------------------------------------------------------------------------
	QDirectX_11_Viewport::viewports.remove(this);
}

bool Sx::QtInterface::QDirectX_11_Viewport::initialize() {
	if ( this->context == nullptr ) {
		std::cerr << "[QDirectX_11_Viewport:initialize] Error: Provided DirectX 11 context is a nullptr." << std::endl;
		std::cerr << "  DirectX 11 viewport not initialized." << std::endl;
		return false;
	}

	//--------------------------------------------------------------------------
	// Initialize the DirectX context if it has not been initialized.
	//--------------------------------------------------------------------------
	if ( this->context->isInitialized() == false ) this->context->initialize();

	if ( this->controller == nullptr ) {
		std::cerr << "[QOpenGL_Viewport:initialize] Warning: DirectX 11 Viewport does not contain a controller." << std::endl;
	}
	else {
		if ( this->controller->isInitialized() == false ) this->controller->initialize();
	}

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory(&sd, sizeof(sd));
	sd.BufferCount = 1;
	sd.BufferDesc.Width = this->width();
	sd.BufferDesc.Height = this->height();
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = this->winId();
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = TRUE;

	HRESULT swapChainResult = this->context->getFactory()->CreateSwapChain(this->context->getDevice(), &sd, &this->swapChain);

	if ( swapChainResult != S_OK ) {
		std::cout << "[QDirectX_11_Viewport:initialize] Error: Cannot initialize DirectX swap chain." << std::endl;
		std::cout << "  Qt DirectX 11 Viewport not initialized." << std::endl;
		return false;
	}

	ID3D11Texture2D *pBackBuffer;
	this->swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
	this->context->getDevice()->CreateRenderTargetView(pBackBuffer, nullptr, &this->backbuffer);
	pBackBuffer->Release();

	QDirectX_11_Viewport::updateViewportSizes();
	this->context->getDeviceContext()->RSSetViewports(QDirectX_11_Viewport::viewports.size(), &QDirectX_11_Viewport::directXViewportSizes[0]);

	return true;
}

void Sx::QtInterface::QDirectX_11_Viewport::render() const {
	if ( this->backbuffer == nullptr || this->context == nullptr || this->swapChain == nullptr ) return;
	if ( this->context->getDeviceContext() == nullptr ) return;

	float color[4] = {1.0f, 0.2f, 0.4f, 1.0f};

	this->context->preRender();
	this->context->getDeviceContext()->OMSetRenderTargets(1, &this->backbuffer, nullptr);
	this->context->getDeviceContext()->ClearRenderTargetView(this->backbuffer, color);
	if ( this->controller != nullptr ) this->controller->render();
	this->swapChain->Present(0, 0);
	this->context->postRender();
}

void Sx::QtInterface::QDirectX_11_Viewport::reRender() const {
	this->render();
}

void Sx::QtInterface::QDirectX_11_Viewport::resize(int width, int height) {
	QWidget::resize(width, height);
	this->view.setSize(this->width(), this->height());

	//--------------------------------------------------------------------------
	// Resize DirectX Swap Chain
	//--------------------------------------------------------------------------
	if ( this->context == nullptr ) return;
	if ( this->context->getDeviceContext() == nullptr ) return;
	this->createBackBuffer(width, height);
	QDirectX_11_Viewport::updateViewportSizes();
	this->context->getDeviceContext()->RSSetViewports(QDirectX_11_Viewport::viewports.size(), &QDirectX_11_Viewport::directXViewportSizes[0]);

	if ( this->controller == nullptr ) return;
	this->controller->resize(width, height);
}

void Sx::QtInterface::QDirectX_11_Viewport::resizeEvent(QResizeEvent* e) {
	this->resize(this->width(), this->height());
}

void Sx::QtInterface::QDirectX_11_Viewport::paintEvent(QPaintEvent* pEvent) {
	this->reRender();
}

/*
 * This function is required because the DirectX 11 interface requires 
 * all viewport sizes to be set at once with an array of D3D11_VIEWPORT
 * structures.
 */
bool Sx::QtInterface::QDirectX_11_Viewport::updateViewportSizes() {
	Util::ArrayList<QDirectX_11_Viewport*>& viewports = QDirectX_11_Viewport::viewports;
	QDirectX_11_Viewport::directXViewportSizes.clear();

	D3D11_VIEWPORT curViewport;
	curViewport.MinDepth = 0.0f;
	curViewport.MaxDepth = 1.0f;
	curViewport.TopLeftX = 0;
	curViewport.TopLeftY = 0;

	for ( unsigned int i = 0; i < viewports.size(); i++ ) {
		curViewport.Width = viewports[i]->width();
		curViewport.Height = viewports[i]->height();
		QDirectX_11_Viewport::directXViewportSizes.add(curViewport);
	}

	return true;
}

bool Sx::QtInterface::QDirectX_11_Viewport::createBackBuffer(int width, int height) {
	if ( this->backbuffer == nullptr ) return false;
	if ( this->swapChain == nullptr ) return false;
	if ( this->context == nullptr ) return false;

	this->backbuffer->Release();
	HRESULT resizeResult = this->swapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);

	ID3D11Texture2D *pBackBuffer;
	this->swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
	this->context->getDevice()->CreateRenderTargetView(pBackBuffer, nullptr, &this->backbuffer);
	pBackBuffer->Release();
	return true;
}
